using System;
using System.Collections;

namespace ICommonLibrary.Collection {

    [Serializable]
    public class WeakRefWrapper {

        private readonly WeakReference _reference;
        private readonly int _hashCode;

        public WeakRefWrapper(object target) {
            _reference = new WeakReference(target);
            _hashCode = target.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (this == obj) {
                return true;
            }

            var that = obj as WeakRefWrapper;
            if (that == null) {
                return false;
            }

            object target = Target;
            if (target == null) {
                // If the reference is dead, we are only equal to ourselves,
                // and this was checked above.
                return false;
            }

            return _hashCode == that._hashCode &&
                   Equals(target, that.Target);
        }

        public override int GetHashCode() {
            return _hashCode;
        }

        public object Target {
            get { return _reference.Target; }
        }

        public bool IsAlive {
            get { return _reference.IsAlive; }
        }

        public static WeakRefWrapper Wrap(object value) {
            return new WeakRefWrapper(value);
        }

        public static object Unwrap(object value) {
            if (value == null) {
                return null;
            }
            return ((WeakRefWrapper)value).Target;
        }
    }

    public class WeakEnumerator : IDictionaryEnumerator {
        private readonly IDictionaryEnumerator _innerEnumerator;

        public WeakEnumerator(IDictionaryEnumerator innerEnumerator) {
            _innerEnumerator = innerEnumerator;
        }

        // Unwrapped key and value stored here so that they
        // do not get collected between calls to MoveNext and Current.
        private object _currentKey, _currentValue;

        public object Key {
            get { return _currentKey; }
        }

        public object Value {
            get { return _currentValue; }
        }

        public DictionaryEntry Entry {
            get { return new DictionaryEntry(_currentKey, _currentValue); }
        }

        public bool MoveNext() {
            // Skip any garbage-collected key/value pairs
            while (true) {
                _currentKey = null;
                _currentValue = null;

                if (!_innerEnumerator.MoveNext()) {
                    return false;
                }

                _currentKey = WeakRefWrapper.Unwrap(_innerEnumerator.Key);
                _currentValue = WeakRefWrapper.Unwrap(_innerEnumerator.Value);

                if (_currentKey != null && _currentValue != null) {
                    break;
                }
            }

            return true;
        }

        public void Reset() {
            _innerEnumerator.Reset();
            _currentKey = _currentValue = null;
        }

        public object Current {
            get { return Entry; }
        }
    }

    [Serializable]
    public class WeakHashtable : IDictionary {
        private readonly Hashtable _innerHashtable = new Hashtable();

        public void Scavenge() {
            var deadKeys = new ArrayList();

            foreach (DictionaryEntry de in _innerHashtable) {
                var key = (WeakRefWrapper)de.Key;
                var value = (WeakRefWrapper)de.Value;

                if (!key.IsAlive || !value.IsAlive) {
                    deadKeys.Add(key);
                }
            }

            foreach (object key in deadKeys) {
                _innerHashtable.Remove(key);
            }
        }

        public bool Contains(object key) {
            return _innerHashtable.Contains(WeakRefWrapper.Wrap(key));
        }

        public void Add(object key, object value) {
            Scavenge();
            _innerHashtable.Add(WeakRefWrapper.Wrap(key), WeakRefWrapper.Wrap(value));
        }

        public void Clear() {
            _innerHashtable.Clear();
        }

        public IDictionaryEnumerator GetEnumerator() {
            return new WeakEnumerator(_innerHashtable.GetEnumerator());
        }

        public void Remove(object key) {
            _innerHashtable.Remove(WeakRefWrapper.Wrap(key));
        }

        public object this[object key] {
            get { return WeakRefWrapper.Unwrap(_innerHashtable[WeakRefWrapper.Wrap(key)]); }
            set {
                Scavenge();
                _innerHashtable[WeakRefWrapper.Wrap(key)] = WeakRefWrapper.Wrap(value);
            }
        }

        public ICollection Keys {
            get { throw new NotImplementedException(); }
        }

        public ICollection Values {
            get { throw new NotImplementedException(); }
        }

        public bool IsReadOnly {
            get { return _innerHashtable.IsReadOnly; }
        }

        public bool IsFixedSize {
            get { return _innerHashtable.IsFixedSize; }
        }

        public void CopyTo(Array array, int index) {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Count of elements in the collection. Unreliable!
        /// </summary>
        public int Count {
            get { return _innerHashtable.Count; }
        }

        public object SyncRoot {
            get { return _innerHashtable.SyncRoot; }
        }

        public bool IsSynchronized {
            get { return _innerHashtable.IsSynchronized; }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
}